/*
 * graph2D
 * Copyright (c) 2009 Shun Moriya <shun@mnu.sakura.ne.jp>
 *
 * The MIT License
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 *
 * The above copyright notice and this permission notice shall be included in
 * all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
 * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM,
 * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR
 * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR
 * THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 */

/*!
 * @file
 * @brief	複数の Component に関するソースファイル
 *
 * このファイルは複数の Component に関するソースファイルです。
 *
 * @author	Shun Moriya <shun@mnu.sakura.ne.jp>
 */

#include "abort.h"
#include "container.h"
#include "graphicDevice.h"
#include "layout.h"
#include "textureContainer.h"
#include <libmana.h>
#include <stdlib.h>
#include <string.h>

// 定義するとローカル座標にフレームの領域を含みません
#define USE_FRAME_SIZE

namespace Graph2D
{
	Vector2 draggingVelocity;

	Container::Container()
		: flags(GRAPH2D_CONTAINER_FLAG_COLOR_AFFECTED)
		, leftFrameSize(0)
		, topFrameSize(0)
		, rightFrameSize(0)
		, bottomFrameSize(0)
    {
		setType(TYPE_CONTAINER);

		memset(frameTexture, NULL, sizeof(frameTexture));

		setClipping(false);
	}

	Container::~Container()
	{
		scrollOffset.zero();
		scrollTargetOffset.zero();
		scrollOffsetLimit.zero();

		for(int i = 0; i <= DIRECTION_SOUTH_WEST; i++)
		{
			unloadFrameTexture(static_cast<Direction>(i));
		}

		// 継承先で呼び出して下さい
		//clear();
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// container
	void Container::clear()
	{
		for(size_t i = 0; i < getCount(); i++)
		{
			set(i, NULL);
		}
	}

	void Container::remove(Component* component)
	{
		if(component)
		{
			for(size_t i = 0; i < getCount(); i++)
			{
				if(get(i) == component)
				{
					set(i, NULL);
					break;
				}
			}
		}
	}

	bool Container::find(Component* component) const
	{
		for(size_t i = 0; i < getCount(); i++)
		{
			if(get(i) == component)
				return true;
		}
		return false;
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// visibility
	void Container::open()
	{
		super::setVisible(true);
	}

	void Container::close()
	{
		super::setVisible(false);
	}

	bool Container::closed() const
	{
		return super::getVisible();
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// mode
	bool Container::getPerspectiveMode() const
	{
		return (flags & GRAPH2D_CONTAINER_FLAG_PERSPECTIVE_MODE) ? true : false;
	}

	void Container::setPerspectiveMode(const bool enable)
	{
		if(enable)
			flags |= GRAPH2D_CONTAINER_FLAG_PERSPECTIVE_MODE;
		else
			flags &= ~GRAPH2D_CONTAINER_FLAG_PERSPECTIVE_MODE;
	}

	void Container::affecte(const bool enable)
	{
		if(enable)
			flags |= GRAPH2D_CONTAINER_FLAG_COLOR_AFFECTED;
		else
			flags &= ~GRAPH2D_CONTAINER_FLAG_COLOR_AFFECTED;
	}

	bool Container::getScrollable() const
	{
		return (flags & GRAPH2D_CONTAINER_FLAG_SCROLLABLE) ? true : false;
	}

	void Container::setScrollable(const bool scrollable)
	{
		if(scrollable)
			flags |= GRAPH2D_CONTAINER_FLAG_SCROLLABLE;
		else
			flags &= ~GRAPH2D_CONTAINER_FLAG_SCROLLABLE;

		setClipping(scrollable);
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// scroll
	const Vector2& Container::getScrollPosition() const
	{
		return scrollOffset;
	}

	void Container::setScrollPosition(const Vector2& localPosition, const bool immediately)
	{
		scrollTargetOffset = localPosition;
		correctScrollTargetOffset();
		if(immediately)
			scrollOffset = scrollTargetOffset;
	}

	void Container::setScrollPosition(const float x, const float y, const bool immediately)
	{
		setScrollPosition(Vector2(x, y), immediately);
	}

	const Vector2& Container::getScrollSize() const
	{
		return scrollOffsetLimit;
	}

	void Container::setScrollSize(const Vector2& size)
	{
		scrollOffsetLimit = size;
	}

	void Container::setScrollSize(const float w, const float h)
	{
		setScrollSize(Vector2(w, h));
	}

	/*
	 * スクロール先ターゲットをスクロール範囲に収めます
	 */
	void Container::correctScrollTargetOffset()
	{
		// スクロール範囲する必要が無いならスクロール範囲を無効にする
		Vector2 limit = getSize() - scrollOffsetLimit;
		if(limit.x > 0.0f)
			limit.x = 0.0f;
		if(limit.y > 0.0f)
			limit.y = 0.0f;

		// スクロール先がスクロール範囲外なら、範囲内に収める
		if(scrollTargetOffset.x > 0.0f)
			scrollTargetOffset.x = 0.0f;
		else if(scrollTargetOffset.x < limit.x)
			scrollTargetOffset.x = limit.x;

		if(scrollTargetOffset.y > 0.0f)
			scrollTargetOffset.y = 0.0f;
		else if(scrollTargetOffset.y < limit.y)
			scrollTargetOffset.y = limit.y;
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// frame
	bool Container::loadFrameTexture(const Container::Direction direction, const std::string& filename/*, const bool repeat_h, const bool repeat_v*/)
	{
		/*
		const unsigned int shift = direction * 2;
		if(repeat_h)
			flags |= (GRAPH2D_CONTAINER_FLAG_FRAME_TEXTURE_REPEAT_H << shift);
		else
			flags &= ~(GRAPH2D_CONTAINER_FLAG_FRAME_TEXTURE_REPEAT_H << shift);

		if(repeat_v)
			flags |= (GRAPH2D_CONTAINER_FLAG_FRAME_TEXTURE_REPEAT_V << shift);
		else
			flags &= ~(GRAPH2D_CONTAINER_FLAG_FRAME_TEXTURE_REPEAT_V << shift);
		*/

		TextureContainer& builder = TextureContainer::getInstance();
		builder.unload(frameTexture[direction]);
		frameTexture[direction] = builder.load(filename);
		return frameTexture[direction] != NULL;
	}

	void Container::unloadFrameTexture(const Container::Direction direction)
	{
		TextureContainer& builder = TextureContainer::getInstance();
		builder.unload(frameTexture[direction]);
		frameTexture[direction] = NULL;
	}

	const Vector2& Container::getFrameTexCoord(const Direction direction) const
	{
		return frameTexCoord[direction];
	}

	void Container::setFrameTexCoord(const Container::Direction direction, const Vector2& frameTexCoord)
	{
		this->frameTexCoord[direction] = frameTexCoord;
	}

	void Container::setFrameTexCoord(const Container::Direction direction, const float u, const float v)
	{
		setFrameTexCoord(direction, Vector2(u, v));
	}

	float Container::getLeftFrameSize() const
	{
		return leftFrameSize;
	}

	void Container::setLeftFrameSize(const float size)
	{
		leftFrameSize = size;
	}

	float Container::getTopFrameSize() const
	{
		return topFrameSize;
	}

	void Container::setTopFrameSize(const float size)
	{
		topFrameSize = size;
	}

	float Container::getRightFrameSize() const
	{
		return rightFrameSize;
	}

	void Container::setRightFrameSize(const float size)
	{
		rightFrameSize = size;
	}

	float Container::getBottomFrameSize() const
	{
		return bottomFrameSize;
	}

	void Container::setBottomFrameSize(const float size)
	{
		bottomFrameSize = size;
	}
#if 0
	void Container::applyLayout(Layout* layout)
	{
		if(layout)
		{
			layout->apply(this);
		}
	}
#endif
	void Container::fit()
	{
		Vector2 max(0, 0);
		for(size_t i = 0; i < getCount(); i++)
		{
			Component* child = get(i);
			if(child == NULL)
				continue;
			if(!child->getEnable())
				continue;
			if(!child->getVisible())
				continue;
			if(child->isKindOfA(TYPE_CONTAINER))
				static_cast<Container*>(child)->fit();

			const Vector2 temporary = child->getPosition() + child->getSize();
			if(max.x < temporary.x)
				max.x = temporary.x;
			if(max.y < temporary.y)
				max.y = temporary.y;
		}

		Vector2 min = max;
		for(size_t i = 0; i < getCount(); i++)
		{
			Component* child = get(i);
			if(child == NULL)
				continue;
			if(!child->getEnable())
				continue;
			if(!child->getVisible())
				continue;

			const Vector2 temporary = child->getPosition();
			if(min.x > temporary.x)
				min.x = temporary.x;
			if(min.y > temporary.y)
				min.y = temporary.y;
		}

		setSize(min.x + max.x + leftFrameSize + rightFrameSize, min.y + max.y + topFrameSize + bottomFrameSize);
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	bool Container::touchesBegan(const Vector2& localPosition)
	{
		if(!getEnable())
			return false;
		if(!getVisible())
			return false;
		if(!isMouseCaptured() && !isInPosition(localPosition))
			return false;

#if defined(USE_FRAME_SIZE)
		// フレームを考慮したローカル座標を計算
		const Vector2 positionInFrame = localPosition - scrollOffset - Vector2(leftFrameSize, topFrameSize);
#else
		const Vector2 positionInFrame = localPosition - scrollOffset;
#endif
		for(size_t i = 0; i < getCount(); i++)
		{
			Component* child = get(i);
			if(child == NULL)
				continue;
			if(!child->getEnable())
				continue;
			if(!child->getVisible())
				continue;

			const Vector2 childLocalPosition = positionInFrame - child->getPosition();
			if(child->touchesBegan(childLocalPosition))
				return true;
		}

		return onTouchesBegan(localPosition);
	}

	bool Container::touchesMoved(const Vector2& localPosition)
	{
		if(!getEnable())
			return false;
		if(!getVisible())
			return false;
		if(!isMouseCaptured() && !isInPosition(localPosition))
			return false;

		// ドラッギング中は子コンポーネントにイベントを通知しない
		if(!dragging)
		{
#if defined(USE_FRAME_SIZE)
			// フレームを考慮したローカル座標を計算
			const Vector2 positionInFrame = localPosition - scrollOffset - Vector2(leftFrameSize, topFrameSize);
#else
			const Vector2 positionInFrame = localPosition - scrollOffset;
#endif
			for(size_t i = 0; i < getCount(); i++)
			{
				Component* child = get(i);
				if(child == NULL)
					continue;
				if(!child->getEnable())
					continue;
				if(!child->getVisible())
					continue;

				const Vector2 childLocalPosition = positionInFrame - child->getPosition();
				if(child->isInPosition(childLocalPosition))
				{
					if(child->touchesMoved(childLocalPosition))
						return true;
				}
			}
		}

		return onTouchesMoved(localPosition);
	}

	bool Container::touchesEnded(const Vector2& localPosition)
	{
		if(!getEnable())
			return false;
		if(!getVisible())
			return false;
		if(!isMouseCaptured() && !isInPosition(localPosition))
			return false;

		// ドラッギング中は子コンポーネントにイベントを通知しない
		if(!dragging)
		{
#if defined(USE_FRAME_SIZE)
			// フレームを考慮したローカル座標を計算
			const Vector2 positionInFrame = localPosition - scrollOffset - Vector2(leftFrameSize, topFrameSize);
#else
			const Vector2 positionInFrame = localPosition - scrollOffset;
#endif
			for(size_t i = 0; i < getCount(); i++)
			{
				Component* child = get(i);
				if(child == NULL)
					continue;
				if(!child->getEnable())
					continue;
				if(!child->getVisible())
					continue;

				const Vector2 childLocalPosition = positionInFrame - child->getPosition();
				if(child->isInPosition(childLocalPosition))
				{
					if(child->touchesEnded(childLocalPosition))
						return true;
				}
			}
		}

		return onTouchesEnded(localPosition);
	}

	bool Container::touchesCancelled(const Vector2& localPosition)
	{
		if(!getEnable())
			return false;
		if(!getVisible())
			return false;
		if(!isMouseCaptured() && !isInPosition(localPosition))
			return false;

		// ドラッギング中は子コンポーネントにイベントを通知しない
		if(!dragging)
		{
			// フレームを考慮したローカル座標を計算
			const Vector2 positionInFrame = localPosition - scrollOffset - Vector2(leftFrameSize, topFrameSize);

			for(size_t i = 0; i < getCount(); i++)
			{
				Component* child = get(i);
				if(child == NULL)
					continue;
				if(!child->getEnable())
					continue;
				if(!child->getVisible())
					continue;

				const Vector2 childLocalPosition = positionInFrame - child->getPosition();
				if(child->isInPosition(childLocalPosition))
				{
					if(child->touchesCancelled(childLocalPosition))
						return true;
				}
			}
		}

		return onTouchesCancelled(localPosition);
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// event
	void Container::onSerialize(mana_stream* stream) const
	{
		super::onSerialize(stream);

		size_t count = 0;
		for(size_t i = 0; i < getCount(); i++)
		{
			if(get(i))
				count++;
		}
		mana_stream_push_unsigned_integer(stream, count);
		mana_stream_push_unsigned_integer(stream, flags);

		for(size_t i = 0; i < getCount(); i++)
		{
			if(get(i))
			{
				mana_stream_mark(stream);
				mana_stream_push_unsigned_integer(stream, i);
				get(i)->onSerialize(stream);
			}
		}
		mana_stream_mark(stream);
	}

	void Container::onDeserialize(mana_stream* stream)
	{
		super::onDeserialize(stream);

		const unsigned int count = mana_stream_pop_unsigned_integer(stream);
		flags = mana_stream_pop_unsigned_integer(stream);

		for(unsigned int i = 0; i < count; i++)
		{
			mana_stream_check(stream);
			const unsigned int index = mana_stream_pop_unsigned_integer(stream);
			set(index, Component::deserialize(stream));
		}
		mana_stream_check(stream);
	}

	void Container::onUpdate(const UpdateInfomation& updateInfomation)
	{
		for(size_t i = 0; i < getCount(); i++)
		{
			Component* child = get(i);
			if(child)
				child->onUpdate(updateInfomation);
		}

		if(getScrollable())
		{
			const Vector2 delta = (scrollTargetOffset - scrollOffset);
			scrollOffset += delta * (1.0f / 0.18f) * updateInfomation.deltaTime;

			if(!dragging)
			{
				scrollTargetOffset += draggingVelocity;
				correctScrollTargetOffset();
			}

			draggingVelocity *= 0.8f;
		}

		super::onUpdate(updateInfomation);
	}

	void Container::onUpdateLayout()
	{
	}

	////////////////////////////////////////////////////////////////////////////////////////////////////
	// event
	bool Container::onDraw(const DrawRect& drawRect)
	{
		if(!super::onDraw(drawRect))
		{
			if(getOwnerDraw() == false)
				return false;
		}

		drawFrame(drawRect);

		DrawRect newDrawRect(drawRect);
		newDrawRect.position += Vector2(getLeftFrameSize(), getTopFrameSize()) * drawRect.scale;
		newDrawRect.size -= Vector2(getLeftFrameSize() + getRightFrameSize(), getTopFrameSize() + getBottomFrameSize()) * drawRect.scale;
		newDrawRect.updateClippingInfomation(drawRect);

		if(getCount() > 0)
		{
#if 0
			if(flags & GRAPH2D_CONTAINER_FLAG_PERSPECTIVE_MODE)
			{
				Component** orderingTable = (Component**)malloc(getCount() * sizeof(Component*));
				if(orderingTable)
				{
					unsigned int j = 0;
					for(unsigned int i = 0; i < getCount(); i++)
					{
						Component* child = children[i];
						if(child && child->getVisible())
						{
							orderingTable[j] = child;
							j++;
						}
					}
					shellSort(orderingTable, j);
					drawChildren(newDrawRect, orderingTable, j);
				}
				free(orderingTable);
			}
#endif
			Vector2 lastScissorPosition;
			Vector2 lastScissorSize;

			if(getClipping())
			{
				GraphicDevice::getScissor(lastScissorPosition, lastScissorSize);
				GraphicDevice::setScissor(newDrawRect.clippingLeftTop, newDrawRect.getClippingSize());
			}

			const Vector2 drawLeftTop = newDrawRect.getDrawLeftTopPosition();
			//const Vector2 drawRightBottom = drawLeftTop + newDrawRect.getDrawSize();
			const Vector2 drawPosition = drawLeftTop + scrollOffset * newDrawRect.scale;

			for(long i = static_cast<long>(getCount()) - 1; i >= 0; --i)
			{
				Component* child = get(i);
				if(child && child->getVisible())
				{
					DrawRect childDrawRect(newDrawRect);
					childDrawRect.position = drawPosition + child->getPosition() * drawRect.getScale();
					childDrawRect.size = child->getSize();
					childDrawRect.scale = child->getScale() * drawRect.getScale();
					childDrawRect.updateClippingInfomation(newDrawRect);
					if(flags & GRAPH2D_CONTAINER_FLAG_COLOR_AFFECTED)
						childDrawRect.color *= getColor();

#if 0
					const Vector2 rightBottom = childDrawRect.position + childDrawRect.size * newDrawRect.scale;
					if(childDrawRect.position.x < drawRightBottom.x)
					{
						if(rightBottom.x >= drawLeftTop.x)
						{
							if(childDrawRect.position.y < drawRightBottom.y)
							{
								if(rightBottom.y >= drawLeftTop.y)
									child->onDraw(childDrawRect);
							}
						}
					}
#else
					child->onDraw(childDrawRect);
#endif
				}
			}

			if(getClipping())
			{
				GraphicDevice::setScissor(lastScissorPosition, lastScissorSize);
			}
		}

		return true;
	}

	/*!
	 * シェルソート
	 * Donald L. Shellが開発した改良挿入ソートのアルゴリズム。高速だが、安定ソートではない。
	 */
	void Container::shellSort(Component** array, const unsigned int length) const
	{
		unsigned int h;

		/* 交換処理の間隔を決めるループ */
		for(h = 1; h < length; h = h * 2 + 1)
			;

		while(h > 1)
		{
			/* hは交換処理の間隔を表す */
			h /= 2;

			for(unsigned int i = h; i < length; i++)
			{
				Component* work = array[i];
				const Vector2& workDepth  = work->getBottomPosition();

				int j = i - h;
				while(workDepth.y > array[j]->getBottomPosition().y)
				{
					array[j + h] = array[j];
					j = j - h;
					if(j < 0)
						break;
				}
				array[j + h] = work;
			}
		}
	}

	void Container::drawChild(const DrawRect& drawRect, Component* component)
	{
		if(component && component->getVisible())
		{
			const Vector2 drawLeftTop = drawRect.getDrawLeftTopPosition();
			const Vector2 drawRightBottom = drawLeftTop + drawRect.getDrawSize();
			const Vector2 drawPosition = drawLeftTop + scrollOffset * drawRect.scale;

			DrawRect newDrawRect(drawRect);
			newDrawRect.position = drawPosition + component->getPosition() * drawRect.scale;
			newDrawRect.size = component->getSize();
			newDrawRect.scale = component->getScale() * drawRect.scale;
			newDrawRect.updateClippingInfomation(drawRect);
			newDrawRect.setScrollOffset(getScrollPosition());

			if(flags & GRAPH2D_CONTAINER_FLAG_COLOR_AFFECTED)
			{
				newDrawRect.color *= getColor();
			}

			const Vector2 rb = newDrawRect.position + newDrawRect.size * drawRect.scale;
			if(newDrawRect.position.x < drawRightBottom.x && rb.x >= drawLeftTop.x && newDrawRect.position.y < drawRightBottom.y && rb.y >= drawLeftTop.y)
			{
				Vector2 lastScissorPosition;
				Vector2 lastScissorSize;

				if(getClipping())
				{
					GraphicDevice::getScissor(lastScissorPosition, lastScissorSize);
					GraphicDevice::setScissor(drawRect.clippingLeftTop, drawRect.getClippingSize());
				}

				component->onDraw(newDrawRect);

				if(getClipping())
				{
					GraphicDevice::setScissor(lastScissorPosition, lastScissorSize);
				}
			}
		}
	}

	void Container::drawFrameParts(const Direction direction, const Vector2& v0, const Vector2& v1, const Vector2& v2, const Vector2& v3, const Color& color, const float width, const float height)
	{
		if(frameTexture[direction])
		{
			frameTexture[direction]->bind();

			const Vector2 textureSize = frameTexture[direction]->getTextureSize();
			const Vector2 uv0((frameTexCoord[direction].x        ) / textureSize.x, (frameTexCoord[direction].y         ) / textureSize.y);
			const Vector2 uv1((frameTexCoord[direction].x + width) / textureSize.x, (frameTexCoord[direction].y         ) / textureSize.y);
			const Vector2 uv2((frameTexCoord[direction].x        ) / textureSize.x, (frameTexCoord[direction].y + height) / textureSize.y);
			const Vector2 uv3((frameTexCoord[direction].x + width) / textureSize.x, (frameTexCoord[direction].y + height) / textureSize.y);

			GraphicDevice::begin(GraphicDevice::TYPE_TEXTURE_COORDINATE, GraphicDevice::TRIANGLES, color);
			GraphicDevice::addTextureCoord(uv0);
			GraphicDevice::addTextureCoord(uv1);
			GraphicDevice::addTextureCoord(uv2);
			GraphicDevice::addTextureCoord(uv1);
			GraphicDevice::addTextureCoord(uv2);
			GraphicDevice::addTextureCoord(uv3);
			GraphicDevice::addVertex(v0);
			GraphicDevice::addVertex(v1);
			GraphicDevice::addVertex(v2);
			GraphicDevice::addVertex(v1);
			GraphicDevice::addVertex(v2);
			GraphicDevice::addVertex(v3);
			GraphicDevice::end();
		}
	}

	void Container::drawFrame(const DrawRect& drawRect)
	{
		if(getVisible() == false || getOwnerDraw() == true)
			return;

		Color baseColor = getDrawColor(drawRect, getColor());
		if(baseColor.a <= 0.0f)
			return;

		GraphicDevice::setBlendMode(static_cast<GraphicDevice::BlendMode>(getBlendMode()));
		GraphicDevice::enableTexture(true);

		//////////////////////////////////////////////////////////////////////
		const Vector2 lt = drawRect.getDrawLeftTopPosition();
		const Vector2 rb = drawRect.getDrawRightBottomPosition();

		const float leftFrameSize = this->leftFrameSize * drawRect.scale.x;
		const float topFrameSize = this->topFrameSize * drawRect.scale.y;
		const float rightFrameSize = this->rightFrameSize * drawRect.scale.x;
		const float bottomFrameSize = this->bottomFrameSize * drawRect.scale.y;

		// 左上頂点
		const Vector2 vltlt(lt.x                 , lt.y                  );
		const Vector2 vltrt(lt.x + leftFrameSize , lt.y                  );
		const Vector2 vltlb(lt.x                 , lt.y + topFrameSize   );
		const Vector2 vltrb(lt.x + leftFrameSize , lt.y + topFrameSize   );
		// 右上頂点
		const Vector2 vrtlt(rb.x - rightFrameSize, lt.y                  );
		const Vector2 vrtrt(rb.x                 , lt.y                  );
		const Vector2 vrtlb(rb.x - rightFrameSize, lt.y + topFrameSize   );
		const Vector2 vrtrb(rb.x                 , lt.y + topFrameSize   );
		// 左下頂点
		const Vector2 vlblt(lt.x                 , rb.y - bottomFrameSize);
		const Vector2 vlbrt(lt.x + leftFrameSize , rb.y - bottomFrameSize);
		const Vector2 vlblb(lt.x                 , rb.y                  );
		const Vector2 vlbrb(lt.x + leftFrameSize , rb.y                  );
		// 右下頂点
		const Vector2 vrblt(rb.x - rightFrameSize, rb.y - bottomFrameSize);
		const Vector2 vrbrt(rb.x                 , rb.y - bottomFrameSize);
		const Vector2 vrblb(rb.x - rightFrameSize, rb.y                  );
		const Vector2 vrbrb(rb.x                 , rb.y                  );
		// フレーム内部のサイズ
		const float widthFrameSize = vrtlb.x - vltrb.x;
		const float heightFrameSize = vlbrt.y - vltrb.y;

		//////////////////////////////////////////////////////////////////////
		// 描画
		drawFrameParts(DIRECTION_CENTER    , vltrb, vrtlb, vlbrt, vrblt, baseColor, widthFrameSize, heightFrameSize);
		drawFrameParts(DIRECTION_NORTH     , vltrt, vrtlt, vltrb, vrtlb, baseColor, widthFrameSize, topFrameSize);
		drawFrameParts(DIRECTION_SOUTH     , vlbrt, vrblt, vlbrb, vrblb, baseColor, leftFrameSize, topFrameSize);
		drawFrameParts(DIRECTION_EAST      , vrtlb, vrtrb, vrblt, vrbrt, baseColor, rightFrameSize, heightFrameSize);
		drawFrameParts(DIRECTION_WEST      , vltlb, vltrb, vlblt, vlbrt, baseColor, leftFrameSize, heightFrameSize);
		drawFrameParts(DIRECTION_NORTH_EAST, vrtlt, vrtrt, vrtlb, vrtrb, baseColor, rightFrameSize, topFrameSize);
		drawFrameParts(DIRECTION_NORTH_WEST, vltlt, vltrt, vltlb, vltrb, baseColor, rightFrameSize, topFrameSize);
		drawFrameParts(DIRECTION_SOUTH_EAST, vrblt, vrbrt, vrblb, vrbrb, baseColor, leftFrameSize, bottomFrameSize);
		drawFrameParts(DIRECTION_SOUTH_WEST, vlblt, vlbrt, vlblb, vlbrb, baseColor, leftFrameSize, bottomFrameSize);
	}

	bool Container::onTouchesBegan(const Vector2& localPosition)
	{
		if(getScrollable())
		{
			// 現在のスクロール値を記録
			toucheBeganScrollOffset = scrollTargetOffset = scrollOffset;
			// ドラッギング用速度を初期化
			draggingVelocity.zero();
		}

		return super::onTouchesBegan(localPosition) | getScrollable();
	}

	bool Container::onTouchesMoved(const Vector2& localPosition)
	{
		if(getScrollable() && dragging)
		{
			// 目標スクロール値を設定
			scrollTargetOffset = toucheBeganScrollOffset + (localPosition - getToucheBeganPosition());
			// スクロール範囲する必要が無いならスクロール範囲を無効にする
			const Vector2 limit = getSize() - scrollOffsetLimit;
			if(limit.x >= 0.0f)
				scrollOffset.x = scrollTargetOffset.x = 0.0f;
			if(limit.y >= 0.0f)
				scrollOffset.y = scrollTargetOffset.y = 0.0f;
			// スクロール速度を計算
			draggingVelocity += scrollTargetOffset - scrollOffset;
			// タッチ中はすぐ反応する
			scrollOffset = scrollTargetOffset;
		}

		return super::onTouchesMoved(localPosition) | getScrollable();
	}

	bool Container::onTouchesEnded(const Vector2& localPosition)
	{
		if(getScrollable())
		{
			// 目標スクロール値を設定
			scrollTargetOffset = toucheBeganScrollOffset + (localPosition - getToucheBeganPosition());
			// スクロール範囲を計算
			correctScrollTargetOffset();
		}

		return super::onTouchesEnded(localPosition) | getScrollable();
	}

	bool Container::onTouchesCancelled(const Vector2& localPosition)
	{
		if(getScrollable())
		{
			// 目標スクロール値を設定
			scrollTargetOffset = toucheBeganScrollOffset + (localPosition - getToucheBeganPosition());
			// スクロール範囲を計算
			correctScrollTargetOffset();
		}

		return super::onTouchesCancelled(localPosition) | getScrollable();
	}

	//! サイズ変更イベント
	bool Container::onSize(const Vector2& size)
	{
		const float w = size.x - getLeftFrameSize() + getRightFrameSize();
		const float h = size.y - getTopFrameSize() + getBottomFrameSize();
		return w >= 0 && h >= 0;
	}
}
